// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Curves/CurveFloat.h"

#include "LyraWeaponInstance.h"
#include "AbilitySystem/LyraAbilitySourceInterface.h"

#include "LyraRangedWeaponInstance.generated.h"

class UPhysicalMaterial;

/**
 * ULyraRangedWeaponInstance
 * 代表远程武器的装备实例，生成并应用于Pawn
 */
UCLASS()
class ULyraRangedWeaponInstance : public ULyraWeaponInstance, public ILyraAbilitySourceInterface
{
	GENERATED_BODY()

public:
	// 构造函数
	ULyraRangedWeaponInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	// 重写PostLoad以处理资源加载后的初始化
	virtual void PostLoad() override;

// 编辑器相关代码
#if WITH_EDITOR
	// 属性更改后处理
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
	// 更新调试可视化
	void UpdateDebugVisualization();
#endif

	// 获取每弹匣的子弹数
	int32 GetBulletsPerCartridge() const
	{
		return BulletsPerCartridge;
	}
	
	/** 返回当前扩散角度（度，直径） */
	float GetCalculatedSpreadAngle() const
	{
		return CurrentSpreadAngle;
	}

	// 获取计算后的扩散角度乘数（如果有首发精度则为0）
	float GetCalculatedSpreadAngleMultiplier() const
	{
		return bHasFirstShotAccuracy ? 0.0f : CurrentSpreadAngleMultiplier;
	}

	// 检查是否具有首发精度
	bool HasFirstShotAccuracy() const
	{
		return bHasFirstShotAccuracy;
	}

	// 获取扩散指数
	float GetSpreadExponent() const
	{
		return SpreadExponent;
	}

	// 获取最大伤害范围
	float GetMaxDamageRange() const
	{
		return MaxDamageRange;
	}

	// 获取子弹追踪扫描半径
	float GetBulletTraceSweepRadius() const
	{
		return BulletTraceSweepRadius;
	}

// 编辑器调试数据
#if WITH_EDITORONLY_DATA
	// 最小热量（调试用）
	UPROPERTY(VisibleAnywhere, Category = "Spread|Fire Params")
	float Debug_MinHeat = 0.0f;
	// 最大热量（调试用）
	UPROPERTY(VisibleAnywhere, Category = "Spread|Fire Params")
	float Debug_MaxHeat = 0.0f;
	// 最小扩散角度（调试用）
	UPROPERTY(VisibleAnywhere, Category="Spread|Fire Params", meta=(ForceUnits=deg))
	float Debug_MinSpreadAngle = 0.0f;
	// 最大扩散角度（调试用）
	UPROPERTY(VisibleAnywhere, Category="Spread|Fire Params", meta=(ForceUnits=deg))
	float Debug_MaxSpreadAngle = 0.0f;
	// 当前热量（调试用）
	UPROPERTY(VisibleAnywhere, Category="Spread Debugging")
	float Debug_CurrentHeat = 0.0f;
	// 当前扩散角度（调试用）
	UPROPERTY(VisibleAnywhere, Category="Spread Debugging", meta = (ForceUnits=deg))
	float Debug_CurrentSpreadAngle = 0.0f;
	// 当前组合扩散角度乘数（调试用）
	UPROPERTY(VisibleAnywhere, Category = "Spread Debugging", meta=(ForceUnits=x))
	float Debug_CurrentSpreadAngleMultiplier = 1.0f;
#endif

	// 扩散指数，影响子弹在中心线周围的聚集紧密程度
	// 较高的值会使子弹更接近中心（默认1.0表示在扩散范围内均匀分布）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, meta=(ClampMin=0.1), Category="Spread|Fire Params")
	float SpreadExponent = 1.0f;

	// 将热量映射到扩散角度的曲线
	// 此曲线的X范围通常设置武器的最小/最大热量范围
	// 此曲线的Y范围用于定义最小和最大扩散角度
	UPROPERTY(EditAnywhere, Category = "Spread|Fire Params")
	FRuntimeFloatCurve HeatToSpreadCurve;

	// 将当前热量映射到单次射击将进一步"加热"的量的曲线
	// 这通常是一条具有单个数据点的平坦曲线，指示一次射击增加多少热量，
	// 但可以是其他形状，以执行诸如通过逐渐增加更多热量来惩罚过热等操作。
	UPROPERTY(EditAnywhere, Category="Spread|Fire Params")
	FRuntimeFloatCurve HeatToHeatPerShotCurve;
	
	// 将当前热量映射到每秒热量冷却速率的曲线
	// 这通常是一条具有单个数据点的平坦曲线，指示热量消散的速度，
	// 但可以是其他形状，以执行诸如通过减慢高热量下的恢复来惩罚过热等操作。
	UPROPERTY(EditAnywhere, Category="Spread|Fire Params")
	FRuntimeFloatCurve HeatToCoolDownPerSecondCurve;

	// 射击后扩散冷却恢复开始前的时间（秒）
	UPROPERTY(EditAnywhere, Category="Spread|Fire Params", meta=(ForceUnits=s))
	float SpreadRecoveryCooldownDelay = 0.0f;

	// 当玩家和武器扩散都处于最小值时，武器是否应具有完美精度
	UPROPERTY(EditAnywhere, Category="Spread|Fire Params")
	bool bAllowFirstShotAccuracy = false;

	// 处于瞄准摄像机模式时的乘数
	UPROPERTY(EditAnywhere, Category="Spread|Player Params", meta=(ForceUnits=x))
	float SpreadAngleMultiplier_Aiming = 1.0f;

	// 静止或移动非常缓慢时的乘数
	// （在StandingStillSpeedThreshold开始淡出，在StandingStillSpeedThreshold + StandingStillToMovingSpeedRange完全消失）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params", meta=(ForceUnits=x))
	float SpreadAngleMultiplier_StandingStill = 1.0f;

	// 过渡到/从静止精度的速率（较高的值更快，但零是瞬时的；@see FInterpTo）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params")
	float TransitionRate_StandingStill = 5.0f;

	// 低于或等于此速度被视为静止
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params", meta=(ForceUnits="cm/s"))
	float StandingStillSpeedThreshold = 80.0f;

	// 不超过StandingStillSpeedThreshold以上的此速度用于逐渐减少静止奖励，直到回到1.0
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params", meta=(ForceUnits="cm/s"))
	float StandingStillToMovingSpeedRange = 20.0f;

	// 蹲伏时的乘数，根据TransitionRate_Crouching平滑混合
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params", meta=(ForceUnits=x))
	float SpreadAngleMultiplier_Crouching = 1.0f;

	// 过渡到/从蹲伏精度的速率（较高的值更快，但零是瞬时的；@see FInterpTo）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params")
	float TransitionRate_Crouching = 5.0f;

	// 跳跃/下落时的扩散乘数，根据TransitionRate_JumpingOrFalling平滑混合
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params", meta=(ForceUnits=x))
	float SpreadAngleMultiplier_JumpingOrFalling = 1.0f;

	// 过渡到/从跳跃/下落精度的速率（较高的值更快，但零是瞬时的；@see FInterpTo）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Spread|Player Params")
	float TransitionRate_JumpingOrFalling = 5.0f;

	// 单个弹匣中发射的子弹数（通常为1，但霰弹枪可能更多）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config")
	int32 BulletsPerCartridge = 1;

	// 此武器可以造成伤害的最大距离
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config", meta=(ForceUnits=cm))
	float MaxDamageRange = 25000.0f;

	// 子弹追踪扫描球体的半径（0.0将导致线追踪）
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon Config", meta=(ForceUnits=cm))
	float BulletTraceSweepRadius = 0.0f;

	// 将距离（厘米）映射到关联游戏效果基础伤害乘数的曲线
	// 如果此曲线中没有数据，则假定武器没有随距离衰减
	UPROPERTY(EditAnywhere, Category = "Weapon Config")
	FRuntimeFloatCurve DistanceDamageFalloff;

	// 影响伤害处理方式的特殊标签列表
	// 这些标签将与命中物体的物理材质中的标签进行比较
	// 如果存在多个标签，乘数将相乘组合
	UPROPERTY(EditAnywhere, Category = "Weapon Config")
	TMap<FGameplayTag, float> MaterialDamageMultiplier;

private:
	// 此武器上次开火的时间（相对于世界时间）
	double LastFireTime = 0.0;
	// 当前热量
	float CurrentHeat = 0.0f;
	// 当前扩散角度（度，直径）
	float CurrentSpreadAngle = 0.0f;
	// 当前是否具有首发精度？
	bool bHasFirstShotAccuracy = false;
	// 当前组合扩散角度乘数
	float CurrentSpreadAngleMultiplier = 1.0f;
	// 当前静止乘数
	float StandingStillMultiplier = 1.0f;
	// 当前跳跃/下落乘数
	float JumpFallMultiplier = 1.0f;
	// 当前蹲伏乘数
	float CrouchingMultiplier = 1.0f;

public:
	/**
	 * 每帧更新武器状态。
	 * @param DeltaSeconds 帧间隔时间
	 */
	void Tick(float DeltaSeconds);

	//~ULyraEquipmentInstance 接口
	/**
	 * 重写的装备接口。装备武器时调用。
	 */
	virtual void OnEquipped();
	/**
	 * 重写的装备接口。卸下武器时调用。
	 */
	virtual void OnUnequipped();
	//~End of ULyraEquipmentInstance 接口

	/**
	 * 增加扩散（开火后调用）。
	 */
	void AddSpread();

	//~ILyraAbilitySourceInterface 接口
	/**
	 * 获取距离衰减乘数。
	 * @param Distance 距离
	 * @param SourceTags 源标签容器
	 * @param TargetTags 目标标签容器
	 * @return 返回距离衰减乘数
	 */
	virtual float GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
	/**
	 * 获取物理材质衰减乘数。
	 * @param PhysicalMaterial 物理材质
	 * @param SourceTags 源标签容器
	 * @param TargetTags 目标标签容器
	 * @return 返回物理材质衰减乘数
	 */
	virtual float GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
	//~End of ILyraAbilitySourceInterface 接口

private:
	/**
	 * 计算扩散角度范围。
	 * @param MinSpread 输出最小扩散角度
	 * @param MaxSpread 输出最大扩散角度
	 */
	void ComputeSpreadRange(float& MinSpread, float& MaxSpread);
	/**
	 * 计算热量范围。
	 * @param MinHeat 输出最小热量
	 * @param MaxHeat 输出最大热量
	 */
	void ComputeHeatRange(float& MinHeat, float& MaxHeat);

	/**
	 * 钳制热量值在最小和最大热量范围内。
	 * @param NewHeat 新的热量值
	 * @return 返回钳制后的热量值
	 */
	inline float ClampHeat(float NewHeat)
	{
		float MinHeat;
		float MaxHeat;
		ComputeHeatRange(/*out*/ MinHeat, /*out*/ MaxHeat);

		return FMath::Clamp(NewHeat, MinHeat, MaxHeat);
	}

	/**
	 * 更新扩散并返回扩散是否处于最小值。
	 * @param DeltaSeconds 帧间隔时间
	 * @return 如果扩散处于最小值返回true
	 */
	bool UpdateSpread(float DeltaSeconds);

	/**
	 * 更新乘数并返回它们是否处于最小值。
	 * @param DeltaSeconds 帧间隔时间
	 * @return 如果乘数处于最小值返回true
	 */
	bool UpdateMultipliers(float DeltaSeconds);
};